package jmt.gui.common.panels;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTable;
import javax.swing.SpinnerNumberModel;
import javax.swing.SpringLayout;
import javax.swing.border.EtchedBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.TableModelEvent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.text.DefaultFormatter;

import jmt.framework.gui.components.JMTFrame;
import jmt.framework.gui.layouts.SpringUtilities;
import jmt.framework.gui.wizard.WizardPanel;
import jmt.gui.common.CommonConstants;
import jmt.gui.common.definitions.ClassDefinition;
import jmt.gui.common.definitions.StationDefinition;
import jmt.gui.common.editors.ImagedComboBoxCellEditorFactory;
import jmt.gui.jmodel.DialogFactory;

/**
 * 
 * @author Riccardo Pinciroli
 *
 */

public class EnergyOptimizerPanel extends WizardPanel implements CommonConstants {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private StationDefinition sd;
	private ClassDefinition cd;
	private Object optimizerKey;

	private BetaValueTable betaTable;
	private HashMap<Integer, String> chNames = new HashMap<Integer, String>();
	private HashMap<Integer, Double> chValues = new HashMap<Integer, Double>();
	private HashMap<Integer, Integer> chNMax = new HashMap<Integer, Integer>();
	
	protected JPanel globalPanel, chPanel, queuePanel;
	protected JSpinner number, winSize, age;
	protected JButton editButton;
	protected JComboBox innerStationList;
	protected JLabel warning;
	protected JCheckBox toDrop;
	
	private String[] innerStationNames;
	
	private final int rowOrder = 100;

	/** Used to display classes with icon */
	protected ImagedComboBoxCellEditorFactory classEditor;
		
	public EnergyOptimizerPanel(StationDefinition sd, ClassDefinition cd, Object key) {
		this.cd = cd;
		this.sd = sd;
		this.optimizerKey = key;
		classEditor = new ImagedComboBoxCellEditorFactory(cd);
		initComponents();
		setData(sd, cd, key);
	}
	
	public void setData(StationDefinition sd, ClassDefinition cd, Object key) {
		this.cd = cd;
		this.sd = sd;
		this.optimizerKey = key;
		chPanel.setBorder(BorderFactory.createTitledBorder(new EtchedBorder(), "Subsystems Properties"));
		globalPanel.setBorder(BorderFactory.createTitledBorder(new EtchedBorder(), "Global Properties"));
		queuePanel.setBorder(BorderFactory.createTitledBorder(new EtchedBorder(), "Queue Properties"));
	}
	
	protected void initComponents() {
		setLayout(new BorderLayout(5, 5));
		
		//Global panel
		globalPanel = new JPanel(new SpringLayout());
		globalPanel.setBorder(BorderFactory.createTitledBorder(new EtchedBorder(), "Global Properties"));
		//Number of subsystems
		JLabel label = new JLabel("Number of subsystems: ");
		JPanel tmpPanel = new JPanel(new BorderLayout(6, 6));
		number = new JSpinner();
		number.setModel(new SpinnerNumberModel(0, 0, NUM_MAX_SUBSYSTEMS, 1));
		if(sd.getMaxCapacity(optimizerKey) != null) {
			number.setValue(sd.getMaxCapacity(optimizerKey));
		}
		tmpPanel.add(number, BorderLayout.WEST);
		label.setLabelFor(number);
		globalPanel.add(label);
		globalPanel.add(tmpPanel);
		//SpringUtilities.makeCompactGrid(globalPanel, 1, 2, 20, 4, 20, 10);
		add(globalPanel, BorderLayout.NORTH);
		JComponent comp = number.getEditor();
	    JFormattedTextField field = (JFormattedTextField) comp.getComponent(0);
	    DefaultFormatter formatter = (DefaultFormatter) field.getFormatter();
	    formatter.setCommitsOnValidEdit(true);
	    number.addChangeListener(new ChangeListener() {
	        @Override
	        public void stateChanged(ChangeEvent e) {
	        	try {
	        		number.commitEdit();
	        	} catch (ParseException pe) {
	        		return;
	        	}
	        	int x = ((Integer) number.getValue()).intValue();
				setNumberOfChannels(x);
				sd.setMaxCapacity(optimizerKey, x);
	        }
	    });
		//ComboBox to edit stations inside the energy optimizer
	    Vector<Object> innerStationKeys = sd.getInnerStationKeys(optimizerKey);
	    Vector<Object> inputInnerStationKeys = sd.getInputInnerStationKeys(optimizerKey); //Input inner stations cannot be modified by the user
	    Vector<Object> innerStationKeysToModify = new Vector<Object>();
	    for(int i=0; i<innerStationKeys.size(); i++) {
	    	if(!inputInnerStationKeys.contains(innerStationKeys.get(i))) {
	    		innerStationKeysToModify.add(innerStationKeys.get(i));
	    	}
	    }
	    innerStationNames = new String[innerStationKeysToModify.size()];
	    for(int i=0; i<innerStationKeysToModify.size(); i++) {
			innerStationNames[i] = sd.getStationName(innerStationKeysToModify.get(i));
	    }
		Vector<String> inputInnerStationNames = new Vector<String>();
		for(int i=0; i<innerStationNames.length; i++) {
			if(!inputInnerStationKeys.contains(innerStationKeys.get(i))) {
				inputInnerStationNames.add(innerStationNames[i]);
			}
		}
	    innerStationList = new JComboBox(innerStationNames);
		tmpPanel = new JPanel(new BorderLayout(6, 6));
		editButton = new JButton("Edit");
		tmpPanel.add(editButton, BorderLayout.WEST);
		globalPanel.add(innerStationList);
		globalPanel.add(tmpPanel);
		editButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				int index = innerStationList.getSelectedIndex();
				if(index >= 0) {
					Object selectedStationKey = sd.getStationByName(innerStationNames[index]);
					StationParameterPanel stationPanel = new StationParameterPanel(sd, cd, selectedStationKey);
					JMTFrame window = new JMTFrame();
					DialogFactory dialogFactory = new DialogFactory(window);
					dialogFactory.getDialog(stationPanel, "Editing " + innerStationNames[index] + " Properties...");
				}
			}
		});
		label = new JLabel("Call center strategy");
		toDrop = new JCheckBox();
		if(sd.getToDrop(optimizerKey) != null) {
			toDrop.setSelected(sd.getToDrop(optimizerKey));
		}
		label.setLabelFor(toDrop);
		tmpPanel = new JPanel(new BorderLayout(6, 6));
		tmpPanel.add(toDrop, BorderLayout.WEST);
		globalPanel.add(label);
		globalPanel.add(tmpPanel);
		toDrop.addChangeListener(new ChangeListener() {
			@Override
			public void stateChanged(ChangeEvent e) {
				if(toDrop.isSelected()) {
					sd.setToDrop(optimizerKey, true);
				} else {
					sd.setToDrop(optimizerKey, false);
				}
			}
		});
		
		SpringUtilities.makeCompactGrid(globalPanel, 1, 6, 20, 4, 20, 10);

		// Panel to specify beta mix and max population for each subsystem
		chPanel = new JPanel(new SpringLayout());
		chPanel.setBorder(BorderFactory.createTitledBorder(new EtchedBorder(), "Subsystems Properties"));
		warning = new JLabel();
		warning.setForeground(Color.RED);
		warning.setVisible(false);
		tmpPanel = new JPanel(new BorderLayout());
		betaTable = new BetaValueTable();
		tmpPanel.add(new WarningScrollTable(betaTable, WARNING_CH));
		//chPanel.add(new WarningScrollTable(betaTable, WARNING_CH));
		chPanel.add(warning);
		chPanel.add(tmpPanel);
		SpringUtilities.makeCompactGrid(chPanel, 2, 1, 20, 4, 20, 10);
		add(chPanel, BorderLayout.CENTER);
		
		//Panel to define parameters used in queue entry strategy (parameters are age and window size)
		queuePanel = new JPanel(new SpringLayout());
		queuePanel.setBorder(BorderFactory.createTitledBorder(new EtchedBorder(), "Queue Properties"));
		label = new JLabel("Window size: ");
		tmpPanel = new JPanel(new BorderLayout(6, 6));
		winSize = new JSpinner();
		winSize.setModel(new SpinnerNumberModel(1, 1, null, 1));
		if(sd.getWindowSize(optimizerKey) != null) {
			winSize.setValue(sd.getWindowSize(optimizerKey));
		}
		tmpPanel.add(winSize, BorderLayout.CENTER);
		label.setLabelFor(winSize);
		queuePanel.add(label);
		queuePanel.add(tmpPanel);
		label = new JLabel("Age: ");
		tmpPanel = new JPanel(new BorderLayout(6, 6));
		age = new JSpinner();
		if(sd.getAge(optimizerKey)!= null) {
			age.setValue(sd.getAge(optimizerKey));
		}
		tmpPanel.add(age, BorderLayout.CENTER);
		label.setLabelFor(age);
		queuePanel.add(label);
		queuePanel.add(tmpPanel);
		SpringUtilities.makeCompactGrid(queuePanel, 1, 4, 20, 4, 20, 10);
		add(queuePanel, BorderLayout.SOUTH);
		winSize.addChangeListener(new ChangeListener() {
	        @Override
	        public void stateChanged(ChangeEvent e) {
	        	try {
	        		winSize.commitEdit();
	        	} catch (ParseException pe) {
	        		return;
	        	}
	        	int x = -1;
				try {
					x = ((Integer) winSize.getValue()).intValue();
				} catch (NumberFormatException nfe) {
				} catch (ClassCastException cce) {
				}
				//if new number is valid, proceed updating number
				if (x != -1) {
					winSize.setValue(x);
					sd.setWindowSize(optimizerKey, x);
				} else {
					//otherwise, reset to 0
					winSize.setValue(new Integer(0));
					sd.setWindowSize(optimizerKey, new Integer(0));
				}
	        }
	    });
		
		age.addChangeListener(new ChangeListener() {
	        @Override
	        public void stateChanged(ChangeEvent e) {
	        	try {
	        		age.commitEdit();
	        	} catch (ParseException pe) {
	        		return;
	        	}
	        	int x = -1;
				try {
					x = ((Integer) age.getValue()).intValue();
				} catch (NumberFormatException nfe) {
				} catch (ClassCastException cce) {
				}
				//if new number is valid, proceed updating number
				if (x != -1) {
					age.setValue(x);
					sd.setAge(optimizerKey, x);
				} else {
					//otherwise, reset to 0
					age.setValue(new Integer(0));
					sd.setAge(optimizerKey, new Integer(0));
				}
	        }
	    });
	}
	
	public void setNumberOfChannels(int newNumber) {
		int oldNumber = chNames.size();
		if(newNumber > oldNumber) {
			addChannel(oldNumber, newNumber);
		} else if (newNumber < oldNumber) {
			deleteChannel(oldNumber, newNumber);
		}
		globalPanel.repaint();
		chPanel.repaint();
	}
	
	protected void refreshComponents() {
		innerStationList.removeAllItems();
		Vector<Object> innerStationKeys = sd.getInnerStationKeys(optimizerKey);
		Vector<Object> inputInnerStationKeys = sd.getInputInnerStationKeys(optimizerKey); //Input inner stations cannot be modified by the user!
		Vector<Object> innerStationKeysToModify = new Vector<Object>();
		for(int i=0; i<innerStationKeys.size(); i++) {
	    	if(!inputInnerStationKeys.contains(innerStationKeys.get(i))) {
	    		innerStationKeysToModify.add(innerStationKeys.get(i));
	    	}
	    }
		innerStationNames = new String[innerStationKeysToModify.size()];
		for(int i=0; i<innerStationKeysToModify.size(); i++) {
			innerStationNames[i] = sd.getStationName(innerStationKeysToModify.get(i)); //Mi serve perché poi uso questa String[] per recuperare le stazioni nel metodo del JButton
			innerStationList.addItem(innerStationNames[i]);
		}
		betaTable.tableChanged(new TableModelEvent(betaTable.getModel()));
		try {
			number.setValue(new Integer(chNames.size()));
		} catch (NumberFormatException nfe) {
		}/*
		if (chNames.size() >= MAX_NUMBER_OF_CLASSES) {
			addClass.setEnabled(false);
		} else {
			addClass.setEnabled(true);
		}*/
	}
	
	protected void addChannel(int oldNumber, int newNumber) {
		if(oldNumber == 0) {
			sd.addStationToEnergyOptimizer(optimizerKey, "router_in_"+sd.getStationName(optimizerKey), STATION_TYPE_ROUTER, INPUT_POSITION);
		}
		for(int i=oldNumber; i<newNumber; i++) {
			chNames.put(i, "SubSystem "+(i+1));
			//It drags old beta in new subsystem. It's useful if you don't have to change them
			for(int j=0; j<cd.getClassKeys().size(); j++) {
				if(oldNumber != 0) {
					Double upValue = chValues.get((i-1)*rowOrder+j);
					chValues.put(i*rowOrder+j, upValue);
				} else {
					chValues.put(i*rowOrder+j, 0.0);
				}
			}
			//It drags old NMax in new subsystem. Same for the beta
			if(oldNumber != 0) {
				Integer upValue = chNMax.get(i-1);
				chNMax.put(i, upValue);
			} else {
				chNMax.put(i, 0);
			}
			String nameFirst = "CPU_Sys"+(i+1)+"_in_"+sd.getStationName(optimizerKey);
			String nameSecond = "I/O_Sys"+(i+1)+"_in_"+sd.getStationName(optimizerKey);
			sd.addStationToEnergyOptimizer(optimizerKey, nameFirst, STATION_TYPE_SERVER, MIDDLE_POSITION);
			sd.addStationToEnergyOptimizer(optimizerKey, nameSecond, STATION_TYPE_SERVER, OUTPUT_POSITION);
			sd.addInnerConnection(optimizerKey, "router_in_"+sd.getStationName(optimizerKey), nameFirst);
			sd.addInnerConnection(optimizerKey, nameFirst, nameSecond);
		}
		refreshComponents();
	}
	
	protected void deleteChannel(int oldNumber, int newNumber) {
		for(int i=oldNumber; i>newNumber; i--) {
			chNames.remove(i-1);
			for(int j=0; j<cd.getClassKeys().size(); j++) {
				chValues.remove((i-1)*rowOrder+j);
			}
			chNMax.remove(i-1);
			sd.removeInnerConnection(optimizerKey, "router_in_"+sd.getStationName(optimizerKey), "CPU_Sys"+i+"_in_"+sd.getStationName(optimizerKey));
			sd.removeInnerConnection(optimizerKey, "CPU_Sys"+(i)+"_in_"+sd.getStationName(optimizerKey), "I/O_Sys"+i+"_in_"+sd.getStationName(optimizerKey));
			for(int j=0; j<sd.getForwardConnections(optimizerKey).size(); j++) {
				sd.removeInnerConnection(optimizerKey, "I/O_Sys"+i+"_in_"+sd.getStationName(optimizerKey), sd.getStationName(sd.getForwardConnections(optimizerKey).get(j)));
			}
			sd.removeStationFromEnergyOptimizer(optimizerKey, "I/O_Sys"+i+"_in_"+sd.getStationName(optimizerKey));
			sd.removeStationFromEnergyOptimizer(optimizerKey, "CPU_Sys"+i+"_in_"+sd.getStationName(optimizerKey));
		}
		if(newNumber == 0) {
			for(int i=0; i<sd.getBackwardConnections(optimizerKey).size(); i++) {
				sd.removeInnerConnection(optimizerKey, sd.getStationName(sd.getBackwardConnections(optimizerKey).get(i)), "router_in_"+sd.getStationName(optimizerKey));
			}
			sd.removeStationFromEnergyOptimizer(optimizerKey, "router_in_"+sd.getStationName(optimizerKey));
		}
		refreshComponents();
	}
	
	@Override
	public String getName() {
		return "Block Parameters";
	}
	
	/**
	 * called by the Wizard before when switching to another panel
	 */
	@Override
	public void lostFocus() {
		// Aborts editing of table
		TableCellEditor editor = betaTable.getCellEditor();
		if (editor != null) {
			editor.stopCellEditing();
		}
	}

	/**
	 * called by the Wizard when the panel becomes active
	 */
	@Override
	public void gotFocus() {
		// Fires a table change event
		betaTable.tableChanged(new TableModelEvent(betaTable.getModel()));
		classEditor.clearCache();
	}
	
	
	
	
	protected class BetaValueTable extends JTable {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		public BetaValueTable() {
			super();
			setModel(new EnergyOptimizerTableModel());
			//this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
			this.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
		}

		@Override
		public TableCellEditor getCellEditor(int row, int column) {
				return super.getCellEditor(row, column);
		}

		@Override
		public TableCellRenderer getCellRenderer(int row, int column) {
			if(column == 0) {
				return getDefaultRenderer(String.class);
			} else if(column == cd.getClassKeys().size()+1) {
				return getDefaultRenderer(Integer.class);
			} else {
				return getDefaultRenderer(Double.class);
			}
		}
		
	}
	
	
	protected class EnergyOptimizerTableModel extends AbstractTableModel {
		
		private static final long serialVersionUID = 1L;
		
		protected String[] colNames = new String[cd.getClassKeys().size()+2];
		protected Class[] colClasses = new Class[cd.getClassKeys().size()+2];
		
		public EnergyOptimizerTableModel() {
			if(sd.getChannelName(optimizerKey) != null && sd.getBetaValue(optimizerKey) != null) {
				chNames = sd.getChannelName(optimizerKey);
				chValues = sd.getBetaValue(optimizerKey);
				chNMax = sd.getChannelNMax(optimizerKey);
			}
			for(int i=0; i<cd.getClassKeys().size()+2; i++) {
				if(i != 0 && i != cd.getClassKeys().size()+1) {
					Vector<Object> keys = cd.getClassKeys();
					colNames[i] = "Target % "+cd.getClassName(keys.get(i-1));
					colClasses[i] = Double.class;
				} else if(i == 0){
					colNames[i] = "Names";
					colClasses[i] = String.class;
				} else {
					colNames[cd.getClassKeys().size()+1] = "N max";
					colClasses[cd.getClassKeys().size()+1] = Integer.class;
				}
			}
		}
		
		public int getRowCount() {
			return chNames.size();
		}
		
		public int getColumnCount() {
			return cd.getClassKeys().size()+2;
		}
		
		@Override
		public String getColumnName(int index) {
			if (index < colNames.length) {
				return colNames[index];
			} else {
				return null;
			}
		}
		
		@Override
		public Class<Object> getColumnClass(int index) {
			if (index < colClasses.length) {
				return colClasses[index];
			} else {
				return Object.class;
			}
		}
		
		@Override
		public boolean isCellEditable(int rowIndex, int columnIndex) {
			if(columnIndex != 0) {
				return true;
			} else {
				return false;
			}
		}
		
		@Override
		public Object getValueAt(int rowIndex, int columnIndex) {
			this.checkBetaValues();
			if(chNames != null && chNames.size() > 0) {
				if(columnIndex == 0) {
					return chNames.get(rowIndex);
				} else if (columnIndex > 0 && columnIndex < colNames.length-1) {
					if(chValues.get(rowIndex*rowOrder+(columnIndex-1)) == null) {
						this.setValueAt(0.0, rowIndex, columnIndex); //Not to leave blank cells
					}
					return chValues.get(rowIndex*rowOrder+(columnIndex-1));
				} else if(columnIndex == colNames.length-1) {
					return chNMax.get(rowIndex);
				} else {
					return null;
				}
			}
			return null;
		}
		
		@Override
		public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
			if(columnIndex == 0) {
				//chNames.remove(rowIndex);
				chNames.put(rowIndex, (String) aValue);
			} else if (columnIndex > 0 && columnIndex < colNames.length-1) {
				Double sum = 0.0;
				for(int col=1; col<getColumnCount()-1; col++) {
					if(chValues.get(rowIndex*rowOrder+(col-1)) != null) {
						sum += chValues.get(rowIndex*rowOrder+(col-1));
					} else {
						sum += 0.0;
					}
				}
				if(chValues.get(rowIndex*rowOrder+(columnIndex-1)) != null) {
					sum -= chValues.get(rowIndex*rowOrder+(columnIndex-1));
				} else {
					sum -= 0.0;
				}
				sum += (Double) aValue;
				if(sum > 1.0) {
					JOptionPane.showMessageDialog(null, "Last value you inserted make the sum of beta values greather than 1.0!", "Error", JOptionPane.ERROR_MESSAGE);
				} else {
					chValues.put(rowIndex*rowOrder+(columnIndex-1), (Double) aValue);
				}
			} else if (columnIndex == colNames.length-1) {
				chNMax.remove(rowIndex);
				chNMax.put(rowIndex, (Integer) aValue);
			}
			sd.setChannelName(optimizerKey, chNames);
			sd.setBetaValue(optimizerKey, chValues);
			sd.setChannelNMax(optimizerKey, chNMax);
		}
		
		/**
		 * It checks if the sum of beta values for each subsystem is less than 1.
		 * If it is true it set the "warning label" to visible, otherwise to not visible.
		 * Beta values whose sum is less than 1 can still be inserted, but a warning message is displayed  if at least one subsystem has a sum less than 1. 
		 */
		private void checkBetaValues() {
			boolean[] betaLessThanOne = new boolean[getRowCount()];
			boolean flag = false; //Flag to check if there is at least a class! 
			for(int row=0; row<getRowCount(); row++) {
				Double sum = 0.0;
				for(int col=1; col<getColumnCount()-1; col++) {
					flag = true;
					if(chValues.get(row*rowOrder+(col-1)) != null) {
						sum += chValues.get(row*rowOrder+(col-1));
					} else {
						sum += 0.0;
					}
				}
				if(flag) {
					if(sum < 1.0) {
						betaLessThanOne[row] = true;
					} else {
						betaLessThanOne[row] = false;
					}
				}
			}
			boolean firstTime = true;
			String temp = null;
			for(int i=0; i<betaLessThanOne.length; i++) {
				if(betaLessThanOne[i]) {
					if(firstTime) {
						temp = chNames.get(i);
						firstTime = false;
					} else {
						temp = temp+", "+chNames.get(i);
					}
				}
			}
			for(int i=0; i<betaLessThanOne.length; i++) {
				if(betaLessThanOne[i]) {
					warning.setText("Sum of betas in "+temp+" is less than 1.0!");
					warning.setVisible(true);
					break;
				} else {
					warning.setVisible(false);
				}
			}
		}
	}
}
